package com.dongjiantong.openplatform.core;

import com.dongjiantong.openplatform.annotations.OpenParamHandler;
import com.dongjiantong.openplatform.entity.*;
import com.dongjiantong.openplatform.exceptions.OPClientException;
import com.dongjiantong.openplatform.utils.ClientUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import com.google.common.hash.Hashing;
import jodd.util.Wildcard;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.lang.annotation.Annotation;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author cao
 * @create 2017-12-13 下午7:28
 **/
@Controller
public class Gateway {
    private static final Logger log = LoggerFactory.getLogger(Gateway.class);
    private final SecurityManager securityManager;
    private final OPInvoker opInvoker;
    private final ObjectMapper objectMapper;
    @Autowired
    private OpenProperties properties;
    @Autowired(
            required = false
    )
    private List<ParamPreHandle> paramPreHandles;

    @Autowired
    public Gateway(SecurityManager securityManager, OPInvoker opInvoker) {
        this.securityManager = securityManager;
        this.opInvoker = opInvoker;
        this.objectMapper = new ObjectMapper();
    }

    @RequestMapping(
            value = {"/api/gateway"},
            produces = {"application/json"}
    )
    @ResponseBody
    public Object execute(HttpServletRequest request) throws Exception {
        boolean isRequestBody = false;
        Object params;
        String requestMethod;
        if(this.isRequestBodyRequest(request)) {
            isRequestBody = true;
            StringBuilder sb = new StringBuilder();
            BufferedReader reader = request.getReader();
            Throwable var6 = null;

            try {
                while((requestMethod = reader.readLine()) != null) {
                    sb.append(requestMethod);
                }
            } catch (Throwable var23) {
                var6 = var23;
                throw var23;
            } finally {
                if(reader != null) {
                    if(var6 != null) {
                        try {
                            reader.close();
                        } catch (Throwable var21) {
                            var6.addSuppressed(var21);
                        }
                    } else {
                        reader.close();
                    }
                }

            }

            RequestBodyData requestBodyData = (RequestBodyData)this.objectMapper.readValue(sb.toString(), RequestBodyData.class);
            params = Maps.newTreeMap();
            ((Map)params).put("appKey", requestBodyData.getAppKey());
            ((Map)params).put("sign", requestBodyData.getSign());
            ((Map)params).put("data", requestBodyData.getData());
            ((Map)params).put("pampasCall", requestBodyData.getPampasCall());
        } else {
            params = this.retrieveParamsFrom(request);
        }

        String method = ((Map)params).get("pampasCall") != null?((Map)params).get("pampasCall").toString():null;
        if(StringUtils.isEmpty(method)) {
            log.error("pampasCall can not empty");
            throw new OPClientException(400, "pampasCall.miss");
        } else {
            String appKey = ((Map)params).get("appKey") != null?((Map)params).get("appKey").toString():null;
            if(this.properties.getMode() == OPMode.GATEWAY && StringUtils.isEmpty(appKey)) {
                log.error("appKey can not empty");
                throw new OPClientException(400, "appKey.miss");
            } else {
                OpClient opClient;
                if(this.properties.getMode() == OPMode.GATEWAY) {
                    OPClientInfo opClientInfo = this.securityManager.findClientByAppKey(appKey);
                    if(opClientInfo == null) {
                        log.error("bad appKey({})", appKey);
                        throw new OPClientException(400, "appKey.incorrect");
                    }

                    //验证签名,开发时可以取消


                     String actual = String.valueOf(((Map) params).remove("sign"));
                     String expected = this.generateSign(opClientInfo.getAppSecret(), (Map) params);
//                     if (!Objects.equals(expected, actual)) {
//                            log.debug("sign mismatch, expected: {}, but actual: {}", expected, actual);
//                            if (this.properties.isDebug()) {
//                                throw new OPClientException(400, "sign.mismatch", new Object[]{expected});
//                            }
//
//                            throw new OPClientException(400, "sign.mismatch");
//                      }


                    opClient = new OpClient(opClientInfo.getClientId(), opClientInfo.getAppKey());
                    if(!this.securityManager.hasPermission(opClient.getClientId(), method)) {
                        log.error("client(id={}) has no permission to call {}", opClient.getClientId(), method);
                        throw new OPClientException(401, "permission.deny");
                    }

                    ((Map)params).put("clientId", opClient.getClientId());
                } else {
                    requestMethod = ((Map)params).get("clientId") != null?((Map)params).get("clientId").toString():null;
                    if(StringUtils.isEmpty(requestMethod)) {
                        log.error("no clientInfo specified");
                        throw new OPClientException(400, "clientInfo.miss");
                    }

                    opClient = new OpClient(Long.valueOf(requestMethod), appKey);
                }

                ClientUtil.put(opClient);
                if(!CollectionUtils.isEmpty(this.paramPreHandles)) {
                    Iterator var30 = this.paramPreHandles.iterator();

                    while(var30.hasNext()) {
                        ParamPreHandle paramPreHandle = (ParamPreHandle)var30.next();
                        if(this.handleMatch(paramPreHandle, method)) {
                            paramPreHandle.handle((Map)params);
                        }
                    }
                }

                Object var32;
                try {
                    requestMethod = request.getMethod();
                    if(request instanceof MultipartHttpServletRequest) {
                        ((Map)params).put("request", request);
                    }

                    var32 = this.dispatch(method, requestMethod, (Map)params, isRequestBody);
                } finally {
                    ClientUtil.clear();
                }

                return var32;
            }
        }
    }

    private boolean isRequestBodyRequest(HttpServletRequest request) {
        try {
            String isRequestBodyRequest = request.getHeader("x-op-req-body");
            return StringUtils.hasText(isRequestBodyRequest) && Boolean.valueOf(isRequestBodyRequest).booleanValue();
        } catch (Exception var3) {
            return false;
        }
    }

    private boolean handleMatch(ParamPreHandle handle, String method) {
        Annotation[] annotations = handle.getClass().getAnnotations();
        Annotation[] var4 = annotations;
        int var5 = annotations.length;

        for(int var6 = 0; var6 < var5; ++var6) {
            Annotation annotation = var4[var6];
            if(annotation instanceof OpenParamHandler) {
                OpenParamHandler openParamHandler = (OpenParamHandler)annotation;
                String[] var9 = openParamHandler.exclusions();
                int var10 = var9.length;

                int var11;
                String pattern;
                for(var11 = 0; var11 < var10; ++var11) {
                    pattern = var9[var11];
                    if(Wildcard.match(method, pattern)) {
                        return false;
                    }
                }

                var9 = openParamHandler.patterns();
                var10 = var9.length;

                for(var11 = 0; var11 < var10; ++var11) {
                    pattern = var9[var11];
                    if(Wildcard.match(method, pattern)) {
                        return true;
                    }
                }
            }
        }

        return false;
    }

    private Map<String, Object> retrieveParamsFrom(HttpServletRequest request) {
        Map<String, Object> params = Maps.newTreeMap();
        Map<String, String[]> parameterMap = request.getParameterMap();
        Iterator var4 = parameterMap.keySet().iterator();

        while(var4.hasNext()) {
            String key = (String)var4.next();
            params.put(key, request.getParameter(key));
        }

        return params;
    }

    private String generateSign(String secret, Map<String, Object> params) {
        String toVerify = Joiner.on('&').withKeyValueSeparator("=").join(params);
        return Hashing.md5().newHasher().putString(toVerify, Charsets.UTF_8).putString(secret, Charsets.UTF_8).hash().toString();
    }

    private Object dispatch(String method, String requestMethod, Map<String, Object> params, boolean isRequestBody) {
        Object result = this.opInvoker.execute(method, requestMethod, params, isRequestBody);
       // return !(result instanceof OPResponse)?OPResponse.ok(result):result;
        return result;
    }
}